/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <hal_lcd.h>
/* includes (standard library, system) ---------------------------------------*/
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_errno.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/

#define LCD_SetPixel(x, y, color)           lcd->methods->setpixel(lcd, x, y, color)
#define LCD_DrawPoint                       LCD_SetPixel
#define LCD_DrawLine(x1, y1, x2, y2, color) hal_lcd_draw_line(lcd, x1, y1, x2, y2, color)

/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

hal_lcd_t *hal_lcd_open(const char *name)
{
    hal_lcd_t *lcd = container_of(hal_open(name, 0), hal_lcd_t, parent);
    if (lcd != NULL && lcd->methods != NULL && lcd->methods->init != NULL) {
        if (lcd->methods->init(lcd) != 0) {
            return NULL;
        }
    }
    return lcd;
}

int hal_lcd_setpixel(hal_lcd_t *lcd, uint32_t x, uint32_t y, uint32_t color)
{
    if (lcd == NULL) {
        return -EINVAL;
    }
    if (lcd->methods->setpixel == NULL) {
        return -ENOSYS;
    }
    return lcd->methods->setpixel(lcd, x, y, color);
}

int hal_lcd_ioctl(hal_lcd_t *lcd, int cmd, ...)
{
    int ret = 0;
    va_list valist;

    if (lcd == NULL) {
        return -EINVAL;
    }
    if (lcd->methods->ioctl == NULL) {
        return -ENOSYS;
    }

    va_start(valist, cmd);
    ret = lcd->methods->ioctl(lcd, cmd, valist);
    va_end(valist);

    return ret;
}

/**
 * hal_lcd_fill - fill a rectangle
 * @lcd:  the lcd device
 * @x1: the x coordinate of the start point
 * @y1: the y coordinate of the start point
 * @w: the width of the rectangle
 * @h: the height of the rectangle
 * @color: the color of the rectangle
 *
 * return: 0 on success, negative value on error
 */
int hal_lcd_fill(hal_lcd_t *lcd, uint32_t x1, uint32_t y1, uint32_t w, uint32_t h, uint32_t color)
{
    if (lcd == NULL) {
        return -EINVAL;
    }
    uint32_t i, j;
    uint32_t x2 = x1 + w;
    uint32_t y2 = y1 + h;
    for (i = y1; i < y2; i++) {
        for (j = x1; j < x2; j++) {
            LCD_SetPixel(j, i, color);
        }
    }
    return 0;
}

int hal_lcd_draw_point(hal_lcd_t *lcd, uint32_t x, uint32_t y, uint32_t color)
{
    if (lcd == NULL) {
        return -EINVAL;
    }
    return LCD_DrawPoint(x, y, color);
}

/**
 * hal_lcd_draw_line - draw a line
 * @lcd:  the lcd device
 * @x1: the x coordinate of the start point
 * @y1: the y coordinate of the start point
 * @x2: the x coordinate of the end point
 * @y2: the y coordinate of the end point
 * @color: the color of the line
 *
 * return: 0 on success, negative value on error
 */
int hal_lcd_draw_line(hal_lcd_t *lcd, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2,
                      uint32_t color)
{
    if (lcd == NULL) {
        return -EINVAL;
    }
    int t;
    int xerr = 0, yerr = 0, dx, dy, distance;   // error x and y
    int sx, sy, x, y;                           // step x and y
    dx = x2 - x1;                               // delta x
    dy = y2 - y1;                               // delta y
    x = x1;                                     // start x
    y = y1;                                     // start y

    // dx = abs(x2 - x1)
    // dy = abs(y2 - y1)
    if (dx > 0) {
        sx = 1;
    } else if (dx == 0) {
        sx = 0;
    } else {
        sx = -1;
        dx = -dx;   // abs
    }
    if (dy > 0) {
        sy = 1;
    } else if (dy == 0) {
        sy = 0;
    } else {
        sy = -1;
        dy = -dy;   // abs
    }

    // distance = max(dx, dy)
    if (dx > dy) {
        distance = dx;
    } else {
        distance = dy;
    }

    // bresenham's line algorithm
    for (t = 0; t < distance + 1; t++) {
        LCD_DrawPoint(x, y, color);
        xerr += dx;
        if (xerr > distance) {
            xerr -= distance;
            x += sx;
        }
        yerr += dy;
        if (yerr > distance) {
            yerr -= distance;
            y += sy;
        }
    }
    return 0;
}

/**
 * hal_lcd_draw_rect - draw a rectangle
 * @lcd:  the lcd device
 * @x1: the x coordinate of the start point
 * @y1: the y coordinate of the start point
 * @x2: the x coordinate of the end point
 * @y2: the y coordinate of the end point
 * @color: the color of the rectangle
 *
 * return: 0 on success, negative value on error
 */
int hal_lcd_draw_rect(hal_lcd_t *lcd, uint32_t x1, uint32_t y1, uint32_t w, uint32_t h,
                      uint32_t color)
{
    if (lcd == NULL) {
        return -EINVAL;
    }
    uint32_t x2 = x1 + w;
    uint32_t y2 = y1 + h;
    LCD_DrawLine(x1, y1, x2, y1, color);
    LCD_DrawLine(x1, y1, x1, y2, color);
    LCD_DrawLine(x1, y2, x2, y2, color);
    LCD_DrawLine(x2, y1, x2, y2, color);
    return 0;
}

/**
 * hal_lcd_draw_circle - draw a circle
 * @lcd:  the lcd device
 * @x0: the x coordinate of the center
 * @y0: the y coordinate of the center
 * @r: the radius of the circle
 * @color: the color of the rectangle
 *
 * return: 0 on success, negative value on error
 */
int hal_lcd_draw_circle(hal_lcd_t *lcd, uint32_t x0, uint32_t y0, uint8_t r, uint32_t color)
{
    if (lcd == NULL) {
        return -EINVAL;
    }
    uint32_t a, b;
    a = 0;
    b = r;
    while (a <= b) {
        // bresenham's circle algorithm
        LCD_DrawPoint(x0 - b, y0 - a, color);   // 3
        LCD_DrawPoint(x0 + b, y0 - a, color);   // 0
        LCD_DrawPoint(x0 - a, y0 + b, color);   // 1
        LCD_DrawPoint(x0 - a, y0 - b, color);   // 2
        LCD_DrawPoint(x0 + b, y0 + a, color);   // 4
        LCD_DrawPoint(x0 + a, y0 - b, color);   // 5
        LCD_DrawPoint(x0 + a, y0 + b, color);   // 6
        LCD_DrawPoint(x0 - b, y0 + a, color);   // 7
        a++;
        if ((a * a + b * b) > (r * r)) {
            b--;
        }
    }
    return 0;
}

int hal_lcd_register(hal_lcd_t *lcd, const char *name, const hal_lcd_methods_t *methods,
                     uintptr_t data)
{
    int ret = 0;

    if (lcd == NULL || name == NULL || methods == NULL || methods->setpixel == NULL) {
        return -EINVAL;
    }

    lcd->parent.name = name;
    lcd->methods = methods;

    if ((ret = hal_register(&lcd->parent)) != 0) {
        return ret;
    }

    return ret;
}
